"""

Name: IOElement.py
Author: Michael Jay Hoover

IOElement is the base data structure for CoffeeTalk

An IOElement has the following attributes:

- name: The identifier for the IOElement
- type: [Optional] Indicates the corresponding class of stream that this IOElement represents. 'type' indicates how 'attribs' should be processed.
- attribs: [Optional] A dictionary containing attributes.
- IOElements: [Optional] A dictionary containing children IOElements.

"""

import copy
import urllib

encodeit = urllib.quote
decodeit = urllib.unquote

class IOElement(object):
	"""The IOElement is the fundamental data structure for CoffeeTalk. """
		
	def __init__(self, name, attribs, encoded = False):
		"""Intialize a new IOElement. 'name' is a string representing the identifier of the IOElement.
		
		Keyword arguments:
		attribs -- a dictionary of optional attributes for this IOElement. (default {}, or empty dictionary)
		
		"""
		self.name = name
				
		if self.name == '':
			raise AttributeError, 'name can not be empty'

		if attribs.has_key('n'):
			del attribs['n']
		
		self.attribs = {}
		self.encodedattribs = {}
		
		if encoded == False:
			self.encodedname = encodeit(name)
			for key,value in attribs.iteritems():
				self.attribs[key] = value
				self.encodedattribs[key] = encodeit(value)
		else:
			self.name = decodeit(name)
			self.encodedname = name
			for key,value in attribs.iteritems():
				self.attribs[key] = decodeit(value)
				self.encodedattribs[key] = value

		self.childelements = {}
	
	def set_name(self,newname):
		self.name = newname
		self.encodedname = encodeit(newname)
		
	def set_attribute(self,name,value):
		self.attribs[name] = value
		self.encodedattribs[name] = encodeit(value)
	
	def __len__(self):
		return len(self.childelements)
	def __iter__(self):
		for key,value in self.childelements.iteritems():
			yield key,value
	def iterkeys(self):
		for key in self.childelements.keys():
			yield key
	def itervalues(self):
		for value in self.childelements.values():
			yield value
	def iteritems(self):
		for key,value in self.childelements.iteritems():
			yield key,value
	def items(self):
		return [(k,v) for k,v in self.childelements.iteritems()]
	def keys(self):
		return list(self.childelements)

	def __repr__(self):
		"""Returns the string representation of the IOElement. """
		return str(self)
		
	def __eq__(self,other):
		"""Returns a boolean value indicating the equality between this IOElement and 'other'. """
		try:
		
			if self.name != other.name:
				raise AttributeError, 'name value not equal'
			
			for key,value in self.attribs.iteritems():
				if other.attribs[key] != value:
					raise AttributeError, 'Attributes are not equal'
				
			for key,value in other.attribs.iteritems():
				if self.attribs[key] != value:
					raise AttributeError, 'Attributes are not equal'
				
			for key,value in self.IOElements.iteritems():
				if other.childelements[key] != value:
					raise AttributeError, 'Children not equal'
				
			for key,value in other.IOElements.iteritems():
				if self.childelements[key] != value:
					raise AttributeError, 'Children not equal'
					
			return True
			
		except:
		
			return False
	
	def __ne__(self,other):
		"""Returns a boolean value indicating the inequality of this IOElement and 'other'. """
		
		if self == other:
			return False
		else:
			return True
	
	def __str__(self):
		"""Returns a string representaion of this IOElement. """
		return self.to_string()
	
	def to_element(self):
		return self
	
	def _to_element(self):
		return self
	
	def to_string(self,env=None):
		"""Returns a string representaion of this IOElement. """
		
		returnstring = ''.join(('<i n="',self.encodedname,'"'))
		if len(self.attribs) > 0:
			for key, value in self.encodedattribs.iteritems():
				returnstring = ''.join((returnstring,' ',str(key),'="',str(value),'"'))
					
		nodecount = 0
		
		for key, value in self.childelements.iteritems():
			if nodecount == 0:
				returnstring = ''.join((returnstring,'>'))
			nodecount = nodecount + 1
			returnstring = ''.join((returnstring ,value.to_string(env=env)))	

		if nodecount == 0:
			returnstring = ''.join((returnstring,' />'))
		else:
			returnstring = ''.join((returnstring,'</i>'))
		return returnstring
	
	def pretty_string(self,level=0):
		
		returnstring = ''.join(( ('    ' * level),'<i n="',self.encodedname,'"'))
		if len(self.attribs) > 0:
			for key,value in self.encodedattribs.iteritems():
				returnstring = ''.join((returnstring,' ',str(key),'="',str(value),'"'))
		nodecount = 0
		
		for key,value in self.childelements.iteritems():
			if nodecount == 0:
				returnstring = ''.join((returnstring,'>\n'))
			nodecount = nodecount + 1
			returnstring = ''.join((returnstring,value.pretty_string(level+1),'\n'))
		if nodecount == 0:
			returnstring = ''.join((returnstring,' />'))
		else:
			returnstring = ''.join((returnstring,('    ' * level),'</i>'))
		return returnstring
		
	def to_opml(self, level = 0, maxdepth = -1):
		"""Returns an OPML string representaion of this IOElement. 
		
		Keyword arguments:
		level -- How many tabs to be used for output (default 0)
		maxdepth -- How many levels deep into the stream should be traversed before stopping (default -1, or unlimited)
		"""
		
		returnstring = '<outline name="' + self.encodedname + '"'
		if len(self.attribs) > 0:
			for key, value in self.encodedattribs.iteritems():
				returnstring = returnstring + ' ' + str(key) + '="' + str(value) + '"'
					
		nodecount = 0
		if maxdepth != 0:
			for key, value in self.childelements.iteritems():
				if nodecount == 0:
					returnstring = returnstring + '>\n'
				nodecount = nodecount + 1
				returnstring = returnstring + ('    ' * (level+1)) + value.to_opml(level=level + 1, maxdepth=maxdepth-1) + '\n'	
		else:
			returnstring = returnstring + ' />'

		if nodecount == 0:
			returnstring = returnstring + ' />'
		else:
			returnstring = returnstring + ('    ' * (level)) + '</outline>'
		return returnstring
	
	def __setitem__(self,name,value):
		"""Add an IOElement to this IOElement. """
		self.childelements[name] = copy.deepcopy(value)

	def add(self,newelement):
		"""Add an IOElement to this IOElement. """
		self.childelements[newelement.name] = copy.deepcopy(newelement)
	
	def remove(self,key):
		"""Remove the child IOElement corresponding to key. """
		if self.childelements.has_key(key):
			del self.childelements[key]
	
	def get_child(self,key, defaultstream = None):
		"""Return the child stream corresponding to key.
		
		Keyword arguments:
		defaultstream -- The IOElement to return if key does not exist.
		"""
		
		if defaultstream != None:
			return self.childelements.get(key,defaultstream)
		else:
			return self.childelements.get(key,IOElement(key))
			
if __name__ == '__main__':
		
	firststream = IOElement('Test1',{'type':'TestStream','Attribute1':'Value1','Attribute2':'Value2'})
	secondstream = IOElement('Test2',{'type':'TestStream','Attribute1':'Value1','Attribute2':'Value2'})
	
	childonestream = IOElement('TestChild1',{'type':'ChildStream','Attribute1':'Value1'})
	childonestreamtwo = IOElement('TestChild12',{'type':'ChildStream','Attribute1':'Value1'})
	childonestreamtwothree = IOElement('TestChild123',{'type':'ChildStream','Attribute1':'Value1'})
	childtwostream = IOElement('TestChild2',{'type':'ChildStream','Attribute1':'Value1','Attribute2':'Value2'})
	
	print 'Child1: ' + str(childonestream)
	print 'Child2: ' + str(childtwostream)
	
	firststream.add(childonestream)
	firststream.add(childonestreamtwo)

	firststream['TestChild123'] = childonestreamtwothree
	secondstream.add(childtwostream)
	
	if firststream == secondstream:
		print 'FirstStream == SecondStream'
	else:
		print 'FirstStream != SecondStream'
		
	print firststream
	print secondstream
	
	for key,value in firststream.iteritems():
		print '%s:%r' % (key,value)

